import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import threading
import pymysql
import os, sys
import re
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
import queue
import time
import json

class MySQLGUITool:
    def __init__(self, root):
        self.root = root
        self.root.title("MySQL批量执行工具")
        self.root.geometry("1200x800")
        self.root.resizable(True, True)
        # 修改图标加载方式，支持打包后和开发环境
        try:
            # 打包后路径
            icon_path = os.path.join(sys._MEIPASS, 'mysql.ico')
        except Exception:
            # 开发环境路径
            icon_path = 'mysql.ico'

        if os.path.exists(icon_path):
            self.root.iconbitmap(icon_path)

        # 多线程相关属性
        self.max_threads = 10  # 最大线程数
        self.thread_pool = None  # 线程池
        self.is_executing = False  # 执行状态标志
        self.cancel_event = threading.Event()  # 取消事件
        self.result_queue = queue.Queue()  # 结果队列

        # 数据库连接参数
        self.db_type = tk.StringVar(value="MySQL")
        self.db_name = tk.StringVar(value="station_gantry")
        self.db_user = tk.StringVar(value="root")
        self.db_password = tk.StringVar(value="kH7@S#2m")
        self.script_file = tk.StringVar(value="script.sql")
        
        # 从数据库配置中提取所有不同的db_app值作为数据库应用列表
        self.db_types = []
        for config in self.db_configs:
            db_app = config.get('database_configs', 'db_app')
            if db_app not in self.db_types:
                self.db_types.append(db_app)
        
        # 加载JSON配置文件
        self.db_configs = []
        self.program_settings = {"max_threads": 10, "default_script": "script.sql"}
        self.load_json_config()
        


        # 服务器列表和执行结果
        self.server_list = []
        self.execution_results = {}

        self.setup_ui()

    def setup_ui(self):
        """设置用户界面"""
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        # 配置网格权重
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(0, weight=1)  # 左侧列可扩展
        main_frame.columnconfigure(1, weight=2)  # 右侧列权重更大
        main_frame.rowconfigure(2, weight=1)  # 中间行可扩展

        # 1. 数据库连接配置区域
        self.create_db_config_section(main_frame)

        # 2. SQL脚本选择区域
        self.create_script_section(main_frame)

        # 3. 服务器列表输入区域
        self.create_server_input_section(main_frame)

        # 4. 结果显示表格
        self.create_results_table(main_frame)

        # 5. 状态栏
        self.create_status_bar()

    def create_db_config_section(self, parent):
        """创建数据库配置区域"""
        # 数据库配置框架
        db_frame = ttk.LabelFrame(parent, text="数据库连接配置", padding="10")
        db_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
        db_frame.columnconfigure(1, weight=1)
        db_frame.columnconfigure(3, weight=1)
        db_frame.columnconfigure(5, weight=1)
        db_frame.columnconfigure(7, weight=1)

        # 数据库应用
        ttk.Label(db_frame, text="数据库应用:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
        db_type_combo = ttk.Combobox(db_frame, textvariable=self.db_type, values=self.db_types, width=15)
        db_type_combo.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 20))
        db_type_combo.bind("<<ComboboxSelected>>", self.on_db_type_changed)

        # 数据库名称
        ttk.Label(db_frame, text="数据库名称:").grid(row=0, column=2, sticky=tk.W, padx=(0, 5))
        ttk.Entry(db_frame, textvariable=self.db_name, width=15).grid(row=0, column=3, sticky=(tk.W, tk.E), padx=(0, 20))

        # 用户名
        ttk.Label(db_frame, text="用户名:").grid(row=0, column=4, sticky=tk.W, padx=(0, 5))
        ttk.Entry(db_frame, textvariable=self.db_user, width=15).grid(row=0, column=5, sticky=(tk.W, tk.E), padx=(0, 20))

        # 密码
        ttk.Label(db_frame, text="密码:").grid(row=0, column=6, sticky=tk.W, padx=(0, 5))
        ttk.Entry(db_frame, textvariable=self.db_password, show="*", width=15).grid(row=0, column=7, sticky=(tk.W, tk.E), padx=(0, 20))

        # 测试连接按钮
        self.test_btn = ttk.Button(db_frame, text="测试连接", command=self.test_connections)
        self.test_btn.grid(row=0, column=8, padx=(10, 0))

        # 添加配置按钮
        ttk.Button(db_frame, text="配置", command=self.show_config_window).grid(row=0, column=9, padx=(10, 0))
        
        # 添加线程数配置
        ttk.Label(db_frame, text="并发数:").grid(row=1, column=0, sticky=tk.W, padx=(0, 5), pady=(5, 0))
        self.thread_count_var = tk.IntVar(value=self.max_threads)
        thread_spinbox = ttk.Spinbox(db_frame, from_=1, to=50, textvariable=self.thread_count_var, width=10)
        thread_spinbox.grid(row=1, column=1, sticky=tk.W, pady=(5, 0))
        
        # 停止按钮
        self.stop_btn = ttk.Button(db_frame, text="停止执行", command=self.stop_execution, state="disabled")
        self.stop_btn.grid(row=1, column=2, padx=(10, 0), pady=(5, 0))

    def create_script_section(self, parent):
        """创建SQL脚本选择区域"""
        script_frame = ttk.LabelFrame(parent, text="SQL脚本文件", padding="10")
        script_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
        script_frame.columnconfigure(1, weight=1)
        script_frame.rowconfigure(1, weight=1)

        # 脚本文件选择
        ttk.Label(script_frame, text="脚本文件:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
        ttk.Entry(script_frame, textvariable=self.script_file, state="readonly").grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
        ttk.Button(script_frame, text="浏览", command=self.browse_script_file).grid(row=0, column=2, padx=(0, 10))
        self.execute_btn = ttk.Button(script_frame, text="执行脚本", command=self.execute_scripts)
        self.execute_btn.grid(row=0, column=3)
        
        # SQL语句输入区域
        ttk.Label(script_frame, text="SQL语句:").grid(row=1, column=0, sticky=tk.NW, padx=(0, 5), pady=(10, 0))
        self.sql_text = scrolledtext.ScrolledText(script_frame, width=40, height=5, wrap=tk.WORD)
        self.sql_text.grid(row=1, column=1, columnspan=2, sticky="nsew", padx=(0, 10), pady=(10, 0))
        
        # 执行SQL按钮
        self.execute_sql_btn = ttk.Button(script_frame, text="执行SQL", command=self.execute_sql)
        self.execute_sql_btn.grid(row=1, column=3, sticky=tk.N, pady=(10, 0))

    def on_db_type_changed(self, event=None):
        """数据库应用变更时的回调函数"""
        selected_type = self.db_type.get()
        
        # 查找匹配的数据库配置
        for config in self.db_configs:
            if config.get('db_app') == selected_type:
                self.db_name.set(config.get('db_name', ''))
                self.db_user.set(config.get('db_user', ''))
                self.db_password.set(config.get('db_password', ''))
                break

    def create_server_input_section(self, parent):
        """创建服务器列表输入区域"""
        server_frame = ttk.LabelFrame(parent, text="MySQL服务器列表", padding="10")
        server_frame.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10), padx=(0, 10))
        server_frame.columnconfigure(0, weight=1)
        server_frame.rowconfigure(1, weight=1)

        # 说明标签
        ttk.Label(server_frame, text="请输入服务器地址，每行一个 (格式: IP:端口，端口默认3306):").grid(row=0, column=0, sticky=tk.W, pady=(0, 5))

        # 服务器列表文本框
        self.server_text = scrolledtext.ScrolledText(server_frame, width=40, height=15, wrap=tk.WORD)
        self.server_text.grid(row=1, column=0, sticky="nsew")

        # 默认示例
        self.server_text.insert(tk.END, "192.168.1.100:3306\n192.168.1.101\n192.168.1.102:3307")

    def create_results_table(self, parent):
        """创建结果显示表格"""
        results_frame = ttk.LabelFrame(parent, text="执行结果", padding="10")
        results_frame.grid(row=2, column=1, rowspan=3, sticky=(tk.W, tk.E, tk.N, tk.S))
        results_frame.columnconfigure(0, weight=1)
        results_frame.rowconfigure(0, weight=1)

        # 创建表格
        columns = ('服务器', '端口', '状态', '执行时间', '错误信息')
        self.results_tree = ttk.Treeview(results_frame, columns=columns, show='headings', height=20)

        # 设置列标题和宽度
        self.results_tree.heading('服务器', text='服务器地址')
        self.results_tree.heading('端口', text='端口')
        self.results_tree.heading('状态', text='执行状态')
        self.results_tree.heading('执行时间', text='执行时间')
        self.results_tree.heading('错误信息', text='错误信息')

        self.results_tree.column('服务器', width=120)
        self.results_tree.column('端口', width=60)
        self.results_tree.column('状态', width=80)
        self.results_tree.column('执行时间', width=150)
        self.results_tree.column('错误信息', width=300)

        # 添加滚动条
        scrollbar = ttk.Scrollbar(results_frame, orient=tk.VERTICAL, command=self.results_tree.yview)
        self.results_tree.configure(yscrollcommand=scrollbar.set)

        self.results_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))

        # 添加进度条
        self.progress_frame = ttk.Frame(results_frame)
        self.progress_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(10, 0))

        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(self.progress_frame, variable=self.progress_var, maximum=100)
        self.progress_bar.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 10))
        self.progress_frame.columnconfigure(0, weight=1)

        self.progress_label = ttk.Label(self.progress_frame, text="就绪")
        self.progress_label.grid(row=0, column=1)

    def create_status_bar(self):
        """创建状态栏"""
        self.status_frame = ttk.Frame(self.root)
        self.status_frame.grid(row=3, column=0, columnspan=2, sticky="ew")

        # 服务器数量统计
        self.server_count_var = tk.StringVar(value="服务器: 0")
        ttk.Label(self.status_frame, textvariable=self.server_count_var).grid(row=0, column=0, padx=5, sticky="w")

        # 执行状态统计
        self.exec_status_var = tk.StringVar(value="执行状态: 成功 0 / 失败 0")
        ttk.Label(self.status_frame, textvariable=self.exec_status_var).grid(row=0, column=1, padx=5, sticky="w")

        # 配置网格权重
        self.status_frame.columnconfigure(0, weight=1)
        self.status_frame.columnconfigure(1, weight=1)


    def browse_script_file(self):
        """浏览选择SQL脚本文件"""
        filename = filedialog.askopenfilename(
            title="选择SQL脚本文件",
            filetypes=[("SQL文件", "*.sql"), ("所有文件", "*.*")]
        )
        if filename:
            self.script_file.set(filename)

    def parse_servers(self):
        """解析服务器列表"""
        server_text = self.server_text.get(1.0, tk.END).strip()
        servers = []

        for line in server_text.split('\n'):
            line = line.strip()
            if line:
                if ':' in line:
                    host, port = line.split(':', 1)
                    try:
                        port = int(port)
                    except ValueError:
                        port = 3306
                else:
                    host = line
                    port = 3306
                servers.append((host.strip(), port))

        return servers

    def test_connections(self):
        """测试数据库连接"""
        if self.is_executing:
            messagebox.showwarning("警告", "正在执行中，请先停止当前操作")
            return
            
        # 重置执行状态
        self.exec_status_var.set("执行状态: 成功 0 / 失败 0")
        servers = self.parse_servers()
        if not servers:
            messagebox.showwarning("警告", "请输入至少一个服务器地址")
            return

        # 清空结果表格
        for item in self.results_tree.get_children():
            self.results_tree.delete(item)

        # 重置进度条
        self.progress_var.set(0)
        self.progress_label.config(text="正在测试连接...")
        
        # 更新按钮状态
        self._set_execution_state(True)

        # 启动测试线程
        threading.Thread(target=self._test_connections_concurrent, args=(servers,), daemon=True).start()

    def _test_connections_concurrent(self, servers):
        """并发测试连接"""
        total_servers = len(servers)
        completed_count = 0
        results = [None] * total_servers  # 预分配结果数组，保持顺序
        
        # 重置取消事件
        self.cancel_event.clear()
        
        # 获取当前线程数设置
        max_workers = self.thread_count_var.get()
        
        try:
            with ThreadPoolExecutor(max_workers=max_workers) as executor:
                self.thread_pool = executor
                
                # 提交所有任务，保持索引映射
                future_to_index = {executor.submit(self._test_single_connection, host, port): i 
                                 for i, (host, port) in enumerate(servers)}
                
                # 处理完成的任务
                for future in as_completed(future_to_index):
                    if self.cancel_event.is_set():
                        break
                        
                    index = future_to_index[future]
                    host, port = servers[index]
                    completed_count += 1
                    
                    try:
                        result = future.result()
                        results[index] = (host, port, result['status'], result['duration'], result['error'])
                    except Exception as e:
                        results[index] = (host, port, "连接失败", "-", str(e))
                    
                    # 更新进度
                    progress = (completed_count / total_servers) * 100
                    self.root.after(0, self._update_progress, progress, f"已测试 {completed_count}/{total_servers}")
                
                # 按顺序更新UI
                self.root.after(0, self._update_results_in_order, results)
                
        except Exception as e:
            self.root.after(0, messagebox.showerror, "错误", f"执行过程中发生错误: {e}")
        finally:
            self.thread_pool = None
            if self.cancel_event.is_set():
                self.root.after(0, self._update_progress, 100, "测试已取消")
            else:
                self.root.after(0, self._update_progress, 100, "测试完成")
            self.root.after(0, self._set_execution_state, False)
    
    def _test_single_connection(self, host, port):
        """测试单个连接"""
        if self.cancel_event.is_set():
            return {'status': '已取消', 'duration': '-', 'error': '用户取消'}
            
        try:
            start_time = datetime.now()
            
            # 尝试连接数据库
            connection = pymysql.connect(
                host=host,
                port=port,
                user=self.db_user.get(),
                password=self.db_password.get(),
                database=self.db_name.get(),
                connect_timeout=10
            )
            connection.close()
            
            end_time = datetime.now()
            duration = (end_time - start_time).total_seconds()
            
            return {'status': '连接成功', 'duration': f"{duration:.2f}秒", 'error': ''}
            
        except Exception as e:
            return {'status': '连接失败', 'duration': '-', 'error': str(e)}

    def _update_result_row(self, host, port, status, duration, error):
        """更新结果表格行"""
        self.results_tree.insert('', tk.END, values=(host, port, status, duration, error))
        # 统计执行状态
        success_count = 0
        fail_count = 0
        for item in self.results_tree.get_children():
            values = self.results_tree.item(item, 'values')
            if values and len(values) > 2:
                if "成功" in values[2]:
                    success_count += 1
                elif "失败" in values[2]:
                    fail_count += 1

        self.exec_status_var.set(f"执行状态: 成功 {success_count} / 失败 {fail_count}")

    def _update_progress(self, value, text):
        """更新进度条"""
        self.progress_var.set(value)
        self.progress_label.config(text=text)

    def execute_scripts(self):
        """执行SQL脚本"""
        if self.is_executing:
            messagebox.showwarning("警告", "正在执行中，请先停止当前操作")
            return
            
        # 重置执行状态
        self.exec_status_var.set("执行状态: 成功 0 / 失败 0")
        if not self.script_file.get():
            messagebox.showwarning("警告", "请先选择SQL脚本文件")
            return

        if not os.path.exists(self.script_file.get()):
            messagebox.showerror("错误", "脚本文件不存在")
            return

        servers = self.parse_servers()
        if not servers:
            messagebox.showwarning("警告", "请输入至少一个服务器地址")
            return

        # 清空结果表格
        for item in self.results_tree.get_children():
            self.results_tree.delete(item)

        # 重置进度条
        self.progress_var.set(0)
        self.progress_label.config(text="正在执行脚本...")
        
        # 更新按钮状态
        self._set_execution_state(True)

        # 启动执行线程
        threading.Thread(target=self._execute_scripts_concurrent, args=(servers,), daemon=True).start()

    def _execute_scripts_concurrent(self, servers):
        """并发执行脚本"""
        total_servers = len(servers)
        completed_count = 0
        results = [None] * total_servers  # 预分配结果数组，保持顺序
        
        # 重置取消事件
        self.cancel_event.clear()
        
        # 读取SQL脚本
        try:
            with open(self.script_file.get(), 'r', encoding='utf-8') as f:
                sql_content = f.read()
        except Exception as e:
            self.root.after(0, messagebox.showerror, "错误", f"读取脚本文件失败: {e}")
            self.root.after(0, self._set_execution_state, False)
            return
            
        # 解析SQL语句（处理delimiter）
        sql_statements = self._parse_sql_statements(sql_content)
        
        # 获取当前线程数设置
        max_workers = self.thread_count_var.get()
        
        try:
            with ThreadPoolExecutor(max_workers=max_workers) as executor:
                self.thread_pool = executor
                
                # 提交所有任务，保持索引映射
                future_to_index = {executor.submit(self._execute_single_script, host, port, sql_statements): i 
                                 for i, (host, port) in enumerate(servers)}
                
                # 处理完成的任务
                for future in as_completed(future_to_index):
                    if self.cancel_event.is_set():
                        break
                        
                    index = future_to_index[future]
                    host, port = servers[index]
                    completed_count += 1
                    
                    try:
                        result = future.result()
                        results[index] = (host, port, result['status'], result['duration'], result['error'])
                    except Exception as e:
                        results[index] = (host, port, "执行失败", "-", str(e))
                    
                    # 更新进度
                    progress = (completed_count / total_servers) * 100
                    self.root.after(0, self._update_progress, progress, f"已执行 {completed_count}/{total_servers}")
                
                # 按顺序更新UI
                self.root.after(0, self._update_results_in_order, results)
                
        except Exception as e:
            self.root.after(0, messagebox.showerror, "错误", f"执行过程中发生错误: {e}")
        finally:
            self.thread_pool = None
            if self.cancel_event.is_set():
                self.root.after(0, self._update_progress, 100, "执行已取消")
            else:
                self.root.after(0, self._update_progress, 100, "执行完成")
            self.root.after(0, self._set_execution_state, False)
    
    def _execute_single_script(self, host, port, sql_statements):
        """在单个服务器上执行脚本"""
        if self.cancel_event.is_set():
            return {'status': '已取消', 'duration': '-', 'error': '用户取消'}
            
        try:
            start_time = datetime.now()
            
            # 连接数据库
            connection = pymysql.connect(
                host=host,
                port=port,
                user=self.db_user.get(),
                password=self.db_password.get(),
                database=self.db_name.get(),
                connect_timeout=10
            )
            
            cursor = connection.cursor()
            
            # 执行SQL语句
            for statement in sql_statements:
                if self.cancel_event.is_set():
                    break
                if statement.strip():
                    cursor.execute(statement)
            
            if not self.cancel_event.is_set():
                connection.commit()
            
            cursor.close()
            connection.close()
            
            end_time = datetime.now()
            duration = (end_time - start_time).total_seconds()
            
            if self.cancel_event.is_set():
                return {'status': '已取消', 'duration': f"{duration:.2f}秒", 'error': '用户取消'}
            else:
                return {'status': '执行成功', 'duration': f"{duration:.2f}秒", 'error': ''}
                
        except Exception as e:
            return {'status': '执行失败', 'duration': '-', 'error': str(e)}

    def _parse_sql_statements(self, sql_content):
        """解析SQL语句，处理delimiter"""
        statements = []
        current_delimiter = ';'
        current_statement = ''

        lines = sql_content.split('\n')

        for line in lines:
            line = line.strip()

            # 跳过注释和空行
            if not line or line.startswith('--') or line.startswith('#'):
                continue

            # 处理delimiter命令
            if line.upper().startswith('DELIMITER'):
                parts = line.split()
                if len(parts) > 1:
                    current_delimiter = parts[1]
                continue

            # 添加到当前语句
            current_statement += line + '\n'

            # 检查是否到达语句结束
            if line.endswith(current_delimiter):
                # 移除delimiter并添加到语句列表
                statement = current_statement.rstrip('\n').rstrip(current_delimiter).strip()
                if statement:
                    statements.append(statement)
                current_statement = ''

        # 处理最后一个语句（如果没有以delimiter结尾）
        if current_statement.strip():
            statements.append(current_statement.strip())

        return statements

    def parse_servers(self):
        """解析服务器列表"""
        server_text = self.server_text.get(1.0, tk.END).strip()
        servers = []

        for line in server_text.split('\n'):
            line = line.strip()
            if line:
                if ':' in line:
                    host, port = line.split(':', 1)
                    try:
                        port = int(port)
                    except ValueError:
                        port = 3306
                else:
                    host = line
                    port = 3306
                servers.append((host.strip(), port))

        # 更新服务器数量显示
        self.server_count_var.set(f"服务器: {len(servers)}")
        return servers

    def _update_result_row(self, host, port, status, duration, error):
        """更新结果表格行"""
        self.results_tree.insert('', tk.END, values=(host, port, status, duration, error))

        # 统计执行状态
        success_count = 0
        fail_count = 0
        for item in self.results_tree.get_children():
            values = self.results_tree.item(item, 'values')
            if values and len(values) > 2:
                if "成功" in values[2]:
                    success_count += 1
                elif "失败" in values[2]:
                    fail_count += 1

        self.exec_status_var.set(f"执行状态: 成功 {success_count} / 失败 {fail_count}")


    def show_config_window(self):
        """显示配置窗口"""
        # 如果配置窗口已存在，则将其置顶并返回
        if hasattr(self, 'config_window') and self.config_window.winfo_exists():
            self.config_window.lift()
            self.config_window.focus_force()
            return

        self.config_window = tk.Toplevel(self.root)
        self.config_window.title("系统配置")
        self.config_window.geometry("600x400")
        self.config_window.resizable(True, True)
        # 添加配置窗口图标
        try:
            # 打包后路径
            icon_path = os.path.join(sys._MEIPASS, 'mysql.ico')
        except Exception:
            # 开发环境路径
            icon_path = 'mysql.ico'

        if os.path.exists(icon_path):
            self.config_window.iconbitmap(icon_path)

        # 窗口关闭时销毁窗口对象
        self.config_window.protocol("WM_DELETE_WINDOW", self._on_config_window_close)
        
        # 创建Notebook
        self.config_notebook = ttk.Notebook(self.config_window)
        self.config_notebook.pack(fill='both', expand=True, padx=10, pady=10)
        
        # 程序设置选项卡
        program_settings_frame = ttk.Frame(self.config_notebook, padding=10)
        self.config_notebook.add(program_settings_frame, text="程序设置")
        
        # 数据库配置选项卡
        db_config_frame = ttk.Frame(self.config_notebook, padding=10)
        self.config_notebook.add(db_config_frame, text="数据库配置")
        
        # 设置网格权重
        program_settings_frame.columnconfigure(1, weight=1)
        db_config_frame.columnconfigure(0, weight=1)
        db_config_frame.rowconfigure(1, weight=1)
        
        # ===== 程序设置选项卡内容 =====
        # 并发数设置
        ttk.Label(program_settings_frame, text="最大并发数:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        self.config_max_threads = ttk.Spinbox(program_settings_frame, from_=1, to=50, width=10)
        self.config_max_threads.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
        self.config_max_threads.set(self.thread_count_var.get())
        
        # 默认脚本路径
        ttk.Label(program_settings_frame, text="默认脚本路径:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
        script_path_frame = ttk.Frame(program_settings_frame)
        script_path_frame.grid(row=1, column=1, sticky=tk.EW, padx=5, pady=5)
        script_path_frame.columnconfigure(0, weight=1)
        
        self.config_script_path = ttk.Entry(script_path_frame)
        self.config_script_path.grid(row=0, column=0, sticky=tk.EW)
        self.config_script_path.insert(0, self.script_file.get())
        
        ttk.Button(script_path_frame, text="浏览", command=self.browse_config_script).grid(row=0, column=1, padx=(5, 0))
        
        # ===== 数据库配置选项卡内容 =====
        # 数据库配置表格
        ttk.Label(db_config_frame, text="数据库配置列表:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        
        # 创建表格
        columns = ('db_app', 'db_name', 'db_user', 'db_password')
        self.db_config_tree = ttk.Treeview(db_config_frame, columns=columns, show='headings', height=10)
        
        # 设置列标题和宽度
        self.db_config_tree.heading('db_app', text='数据库应用')
        self.db_config_tree.heading('db_name', text='数据库名称')
        self.db_config_tree.heading('db_user', text='用户名')
        self.db_config_tree.heading('db_password', text='密码')
        
        self.db_config_tree.column('db_app', width=100)
        self.db_config_tree.column('db_name', width=150)
        self.db_config_tree.column('db_user', width=100)
        self.db_config_tree.column('db_password', width=150)
        
        # 添加滚动条
        db_config_scrollbar = ttk.Scrollbar(db_config_frame, orient=tk.VERTICAL, command=self.db_config_tree.yview)
        self.db_config_tree.configure(yscrollcommand=db_config_scrollbar.set)
        
        self.db_config_tree.grid(row=1, column=0, sticky="nsew", padx=5, pady=5)
        db_config_scrollbar.grid(row=1, column=1, sticky="ns", pady=5)
        
        # 填充数据库配置表格
        for config in self.db_configs:
            self.db_config_tree.insert('', tk.END, values=(
                config.get('db_app', ''),
                config.get('db_name', ''),
                config.get('db_user', ''),
                config.get('db_password', '')
            ))
        
        # 编辑区域
        edit_frame = ttk.LabelFrame(db_config_frame, text="编辑数据库配置", padding=10)
        edit_frame.grid(row=2, column=0, columnspan=2, sticky="ew", padx=5, pady=5)
        
        # 数据库应用
        ttk.Label(edit_frame, text="数据库应用:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        self.config_db_type = ttk.Entry(edit_frame, width=15)
        self.config_db_type.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
        
        # 数据库名称
        ttk.Label(edit_frame, text="数据库名称:").grid(row=0, column=2, sticky=tk.W, padx=5, pady=5)
        self.config_db_name = ttk.Entry(edit_frame, width=20)
        self.config_db_name.grid(row=0, column=3, sticky=tk.W, padx=5, pady=5)
        
        # 用户名
        ttk.Label(edit_frame, text="用户名:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
        self.config_db_user = ttk.Entry(edit_frame, width=15)
        self.config_db_user.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5)
        
        # 密码
        ttk.Label(edit_frame, text="密码:").grid(row=1, column=2, sticky=tk.W, padx=5, pady=5)
        self.config_db_password = ttk.Entry(edit_frame, width=20, show="*")
        self.config_db_password.grid(row=1, column=3, sticky=tk.W, padx=5, pady=5)
        
        # 按钮区域
        button_frame = ttk.Frame(edit_frame)
        button_frame.grid(row=2, column=0, columnspan=4, pady=10)
        
        ttk.Button(button_frame, text="添加", command=self.add_db_config).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="更新", command=self.update_db_config).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="删除", command=self.delete_db_config).pack(side=tk.LEFT, padx=5)
        
        # 表格选择事件
        self.db_config_tree.bind("<<TreeviewSelect>>", self.on_db_config_selected)
        
        # 数据库应用列表选项卡内容已删除
        
        # 底部保存按钮
        ttk.Button(self.config_window, text="保存配置", command=self.save_config).pack(pady=10)

    def browse_config_script(self):
        """浏览选择默认SQL脚本文件"""
        filename = filedialog.askopenfilename(
            title="选择默认SQL脚本文件",
            filetypes=[("SQL文件", "*.sql"), ("所有文件", "*.*")]
        )
        if filename:
            self.config_script_path.delete(0, tk.END)
            self.config_script_path.insert(0, filename)
    
    def on_db_config_selected(self, event):
        """数据库配置表格选择事件"""
        selected_items = self.db_config_tree.selection()
        if selected_items:
            item = selected_items[0]
            values = self.db_config_tree.item(item, 'values')
            if values:
                self.config_db_type.set(values[0])
                self.config_db_name.delete(0, tk.END)
                self.config_db_name.insert(0, values[1])
                self.config_db_user.delete(0, tk.END)
                self.config_db_user.insert(0, values[2])
                self.config_db_password.delete(0, tk.END)
                self.config_db_password.insert(0, values[3])
    
    def add_db_config(self):
        """添加数据库配置"""
        db_app = self.config_db_type.get()
        db_name = self.config_db_name.get()
        db_user = self.config_db_user.get()
        db_password = self.config_db_password.get()
        
        if not db_app or not db_name:
            messagebox.showwarning("警告", "数据库应用和名称不能为空")
            return
        
        # 检查是否已存在相同配置
        for config in self.db_configs:
            if config.get('db_app') == db_app and config.get('db_name') == db_name:
                messagebox.showwarning("警告", f"已存在相同的数据库配置: {db_app} - {db_name}")
                return
        
        # 添加到配置列表
        new_config = {
            'db_app': db_app,
            'db_name': db_name,
            'db_user': db_user,
            'db_password': db_password
        }
        self.db_configs.append(new_config)
        
        # 添加到表格
        self.db_config_tree.insert('', tk.END, values=(db_app, db_name, db_user, db_password))
        
        # 清空输入框
        self.config_db_name.delete(0, tk.END)
        self.config_db_user.delete(0, tk.END)
        self.config_db_password.delete(0, tk.END)
    
    def update_db_config(self):
        """更新数据库配置"""
        selected_items = self.db_config_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要更新的配置")
            return
        
        item = selected_items[0]
        old_values = self.db_config_tree.item(item, 'values')
        
        db_app = self.config_db_type.get()
        db_name = self.config_db_name.get()
        db_user = self.config_db_user.get()
        db_password = self.config_db_password.get()
        
        if not db_app or not db_name:
            messagebox.showwarning("警告", "数据库应用和名称不能为空")
            return
        
        # 更新表格
        self.db_config_tree.item(item, values=(db_app, db_name, db_user, db_password))
        
        # 更新配置列表
        for i, config in enumerate(self.db_configs):
            if config.get('db_app') == old_values[0] and config.get('db_name') == old_values[1]:
                self.db_configs[i] = {
                    'db_app': db_app,
                    'db_name': db_name,
                    'db_user': db_user,
                    'db_password': db_password
                }
                break
    
    def delete_db_config(self):
        """删除数据库配置"""
        selected_items = self.db_config_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要删除的配置")
            return
        
        item = selected_items[0]
        values = self.db_config_tree.item(item, 'values')
        
        # 从表格中删除
        self.db_config_tree.delete(item)
        
        # 从配置列表中删除
        for i, config in enumerate(self.db_configs):
            if config.get('db_app') == values[0] and config.get('db_name') == values[1]:
                del self.db_configs[i]
                break
    
    
    def save_config(self):
        """保存配置"""
        # 更新程序设置
        self.max_threads = int(self.config_max_threads.get())
        self.thread_count_var.set(self.max_threads)
        self.script_file.set(self.config_script_path.get())
        
        # 更新程序设置到JSON
        self.program_settings['max_threads'] = self.max_threads
        self.program_settings['default_script'] = self.script_file.get()
        
        # 保存到JSON文件
        self.save_json_config()
        
        # 如果有数据库配置，使用第一个作为默认
        if self.db_configs:
            first_config = self.db_configs[0]
            self.db_type.set(first_config.get('db_app', 'MySQL'))
            self.db_name.set(first_config.get('db_name', ''))
            self.db_user.set(first_config.get('db_user', ''))
            self.db_password.set(first_config.get('db_password', ''))
        
        messagebox.showinfo("提示", "配置已保存")
        self.config_window.destroy()
    
    def _update_results_in_order(self, results):
        """按顺序更新结果到表格"""
        for result in results:
            if result is not None:
                host, port, status, duration, error = result
                self.results_tree.insert('', tk.END, values=(host, port, status, duration, error))
        
        # 统计执行状态
        success_count = 0
        fail_count = 0
        for item in self.results_tree.get_children():
            values = self.results_tree.item(item, 'values')
            if values and len(values) > 2:
                if "成功" in values[2]:
                    success_count += 1
                elif "失败" in values[2]:
                    fail_count += 1
        
        self.exec_status_var.set(f"执行状态: 成功 {success_count} / 失败 {fail_count}")
    
    def _set_execution_state(self, is_executing):
        """设置执行状态并更新UI"""
        self.is_executing = is_executing
        
        # 更新按钮状态
        if is_executing:
            self.test_btn.config(state="disabled")
            self.execute_btn.config(state="disabled")
            self.execute_sql_btn.config(state="disabled")
            self.stop_btn.config(state="normal")
        else:
            self.test_btn.config(state="normal")
            self.execute_btn.config(state="normal")
            self.execute_sql_btn.config(state="normal")
            self.stop_btn.config(state="disabled")
    
    def stop_execution(self):
        """停止当前执行"""
        if self.is_executing:
            self.cancel_event.set()
            if self.thread_pool:
                # 尝试取消未开始的任务
                try:
                    self.thread_pool.shutdown(wait=False)
                except:
                    pass
            self.progress_label.config(text="正在停止...")
            messagebox.showinfo("提示", "正在停止执行，请稍候...")
            
    def execute_sql(self):
        """执行SQL语句"""
        if self.is_executing:
            messagebox.showwarning("警告", "正在执行中，请先停止当前操作")
            return
            
        # 重置执行状态
        self.exec_status_var.set("执行状态: 成功 0 / 失败 0")
        sql_content = self.sql_text.get(1.0, tk.END).strip()
        if not sql_content:
            messagebox.showwarning("警告", "请输入SQL语句")
            return

        servers = self.parse_servers()
        if not servers:
            messagebox.showwarning("警告", "请输入至少一个服务器地址")
            return

        # 清空结果表格
        for item in self.results_tree.get_children():
            self.results_tree.delete(item)

        # 重置进度条
        self.progress_var.set(0)
        self.progress_label.config(text="正在执行SQL...")
        
        # 更新按钮状态
        self._set_execution_state(True)

        # 启动执行线程
        threading.Thread(target=self._execute_sql_concurrent, args=(servers, sql_content), daemon=True).start()
        
    def _execute_sql_concurrent(self, servers, sql_content):
        """并发执行SQL语句"""
        total_servers = len(servers)
        completed_count = 0
        results = [None] * total_servers  # 预分配结果数组，保持顺序
        
        # 重置取消事件
        self.cancel_event.clear()
        
        # 解析SQL语句（处理delimiter）
        sql_statements = self._parse_sql_statements(sql_content)
        
        # 获取当前线程数设置
        max_workers = self.thread_count_var.get()
        
        try:
            with ThreadPoolExecutor(max_workers=max_workers) as executor:
                self.thread_pool = executor
                
                # 提交所有任务，保持索引映射
                future_to_index = {executor.submit(self._execute_single_script, host, port, sql_statements): i 
                                 for i, (host, port) in enumerate(servers)}
                
                # 处理完成的任务
                for future in as_completed(future_to_index):
                    if self.cancel_event.is_set():
                        break
                        
                    index = future_to_index[future]
                    host, port = servers[index]
                    completed_count += 1
                    
                    try:
                        result = future.result()
                        results[index] = (host, port, result['status'], result['duration'], result['error'])
                    except Exception as e:
                        results[index] = (host, port, "执行失败", "-", str(e))
                    
                    # 更新进度
                    progress = (completed_count / total_servers) * 100
                    self.root.after(0, self._update_progress, progress, f"已执行 {completed_count}/{total_servers}")
                
                # 按顺序更新UI
                self.root.after(0, self._update_results_in_order, results)
                
        except Exception as e:
            self.root.after(0, messagebox.showerror, "错误", f"执行过程中发生错误: {e}")
        finally:
            self.thread_pool = None
            if self.cancel_event.is_set():
                self.root.after(0, self._update_progress, 100, "执行已取消")
            else:
                self.root.after(0, self._update_progress, 100, "执行完成")
            self.root.after(0, self._set_execution_state, False)

    def _on_config_window_close(self):
        """处理配置窗口关闭事件"""
        if hasattr(self, 'config_window'):
            self.config_window.destroy()
            del self.config_window
            
    def load_json_config(self):
        """从JSON文件加载配置"""
        try:
            if os.path.exists('db_config.json'):
                with open('db_config.json', 'r', encoding='utf-8') as f:
                    config_data = json.load(f)
                    
                    # 加载数据库配置
                    if 'database_configs' in config_data and config_data['database_configs']:
                        self.db_configs = config_data['database_configs']
                        # 设置默认数据库配置为第一个
                        first_config = self.db_configs[0]
                        self.db_type.set(first_config.get('db_app', 'MySQL'))
                        self.db_name.set(first_config.get('db_name', ''))
                        self.db_user.set(first_config.get('db_user', ''))
                        self.db_password.set(first_config.get('db_password', ''))
                        
                        # 从数据库配置中提取所有不同的db_app值作为数据库应用列表
                        self.db_types = []
                        for config in self.db_configs:
                            db_app = config.get('db_app', 'MySQL')
                            if db_app not in self.db_types:
                                self.db_types.append(db_app)
                    
                    # 加载程序设置
                    if 'program_settings' in config_data:
                        self.program_settings = config_data['program_settings']
                        self.max_threads = self.program_settings.get('max_threads', 10)
                        self.thread_count_var.set(self.max_threads)
                        self.script_file.set(self.program_settings.get('default_script', 'script.sql'))
        except Exception as e:
            print(f"加载配置文件失败: {e}")
    
    def save_json_config(self):
        """保存配置到JSON文件"""
        try:
            # 更新程序设置
            self.program_settings['max_threads'] = self.thread_count_var.get()
            self.program_settings['default_script'] = self.script_file.get()
            
            config_data = {
                'database_configs': self.db_configs,
                'program_settings': self.program_settings
            }
            
            with open('db_config.json', 'w', encoding='utf-8') as f:
                json.dump(config_data, f, ensure_ascii=False, indent=4)
        except Exception as e:
            messagebox.showerror("错误", f"保存配置文件失败: {e}")
    
    def on_db_type_changed(self, event=None):
        """数据库应用变更时的回调函数"""
        selected_type = self.db_type.get()
        
        # 查找匹配的数据库配置
        for config in self.db_configs:
            if config.get('db_app') == selected_type:
                self.db_name.set(config.get('db_name', ''))
                self.db_user.set(config.get('db_user', ''))
                self.db_password.set(config.get('db_password', ''))
                break

def main():
    root = tk.Tk()
    app = MySQLGUITool(root)
    root.mainloop()

if __name__ == "__main__":
    main()